package Conexao;

import Estruturas.Manipuladores;
import Estruturas.ReturnIP;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import Opcoes.*;
import Pacotes.*;
import Conexao.*;
import Paralelos.Observador;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;

    /**
    *
    *
    *@param Não recebe parâmetros, é utilizada para realizar o controle de sockets, responsável por disparar threads responsáveis pelo fluxo cliente e o fluxo servidor
    *
    *
    **/


public class Gerenciador {

    private Socket[] gnutellaCloud;
    private DataInputStream[] gnuDIS;
    private DataOutputStream[] gnuDOS;

    private int posicaoCloud;
    private ExecutorService apps = Executors.newFixedThreadPool(3);
    private LinkedList<ReturnIP> returnId = new LinkedList<ReturnIP>();
    private boolean srv_done;

    public Gerenciador(){
        
        posicaoCloud = 0;
        DadosUsuario.Dados.setServent_id();

        gnutellaCloud = new Socket[Conexoes.MAX_CONEXOES_G2];
        gnuDIS = new DataInputStream[Conexoes.MAX_CONEXOES_G2];
        gnuDOS = new DataOutputStream[Conexoes.MAX_CONEXOES_G2];
        srv_done = false;


        startServer();
        startClient();

    }

    public void startServer ()
    {
        try {

            apps.execute(new FluxoServer(apps, this));
            apps.execute(new FluxoCliente(apps, this));

            apps.execute(new FluxoServer(apps, this));
            apps.execute(new Observador(apps, this));

        } catch (Exception e) {
                e.printStackTrace();
        }
    }


    public void startClient ()
    {
        try {

            System.out.println("Connect? (y/n)");
            InputStreamReader in = new InputStreamReader(System.in);
            if(in.read() == 'y')
            {
                Download down = new Download(1,"teste.txt");
                apps.execute(down);
//                apps.execute(new FluxoCliente(apps, this));
//                Thread.sleep(2000);
//                System.out.println("Pinging...");
//                send(new Ping());
//                Thread.sleep(10000);
//                send(new Query("Asda"));
            }

        } catch (Exception e) {
                e.printStackTrace();
        }
    }

    //envia ping para todos que está conectado
    public boolean send (Ping pck)
    {
        System.out.println("Enviando Ping");
        int i;
        try {
            for( i=0 ; i<posicaoCloud ; i++)
            {
                gnuDOS[i].writeBytes(pck.make());
            }
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }

    //envia ping para todos que está conectado
    public boolean send (Query pck)
    {
        System.out.println("Enviando Query");
        int i;
        try {
            for( i=0 ; i<posicaoCloud ; i++)
            {
                gnuDOS[i].writeBytes(pck.make());
            }
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }
    //envia o query hit para quem pediu o query(Socket_index)
    public boolean send (QueryHit pck, int Socket_index)
    {
        System.out.println("Enviando QueryHit");
        int i;
        try {
            gnuDOS[Socket_index].writeBytes(pck.make());
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }
    //envia pong para quem enviou ping(Socket_index)
    public boolean send (Pong pck, int Socket_index)
    {
        System.out.println("Enviando Pong");
        int i;
        try {
            gnuDOS[Socket_index].writeBytes(pck.make());
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }
    //push
    public boolean send (Push pck, int Socket_index)
    {
        System.out.println("Enviando Push");
        int i;
        try {
            gnuDOS[Socket_index].writeBytes(pck.make());
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }


    public void setServidorDone ()
    {
        srv_done = true;
    }


    public boolean verificar_fluxos ()
    {
        byte[] packet_header;
        int i = -1,j,packTam;
        try {
            //DEBUG
            System.out.println("Verificando...");
            for( i=0 ; i < posicaoCloud ; i++)
            {
                packTam = gnuDIS[i].available();
                //DEBUG
                System.out.printf("Sock:%d\tBuff:%d\n",i,packTam);

                if(gnuDIS[i].available() > 0)
                {
                    packet_header = new byte[23];
                    gnuDIS[i].read(packet_header);
                    //verifica se é o id do ping
                    if (packet_header[16] == Pacotes.PAYLOAD_OF_PING)
                    {
                        String descCopy;
                        String retIP;

                        Pong pong = new Pong();
                        descCopy = Manipuladores.extrairDados(packet_header,0,16);
                        retIP = Manipuladores.getIP(gnutellaCloud[i].getInetAddress());

                        //pong.setId(descCopy);
                        ReturnIP returnIP = new ReturnIP(retIP, descCopy, 1);
                        //adiciona na lista
                        returnId.add(returnIP);

                        //DEBUG
                        System.out.println("PING recebido! Enviando pong...");

                        send(pong,i);
                    }
                    if (packet_header[16] == Pacotes.PAYLOAD_OF_PONG)
                    {
                        System.out.println("PONG recebido!");

                        int pong_size = Manipuladores.byteArrayToInt(packet_header, 19);
                        //System.out.println("pong_size:" + pong_size);

                        byte[] pong_buff = new byte[pong_size];
                        gnuDIS[i].read(pong_buff);
                        //System.out.println("PONG:" + Manipuladores.extrairDados(pong_buff, 0, pong_size) + ";");
                    }
                    if (packet_header[16] == Pacotes.PAYLOAD_OF_QUERY)
                    {
                        System.out.println("Query recebido!");

                        int query_size = Manipuladores.byteArrayToInt(packet_header, 19);
                        //System.out.println("query_size:" + query_size);

                        byte[] query_buff = new byte[query_size];
                        gnuDIS[i].read(query_buff);
                        //System.out.println("Query:" + Manipuladores.extrairDados(query_buff, 2, query_size) + ";");

                        send(new QueryHit(),i);

                    }
                    if (packet_header[16] == Pacotes.PAYLOAD_OF_QUERY_HIT)
                    {
                        System.out.println("Query hit recebido!");

                        int query_size = Manipuladores.byteArrayToInt(packet_header, 19);
                        System.out.println("query_hit_size:" + query_size);

                        byte[] query_buff = new byte[query_size];
                        gnuDIS[i].read(query_buff);
                        System.out.println("Hits:" + Manipuladores.extrairDados(query_buff, 2, query_size) + ";");

                        //send(new Push(1),i);

                    }
                    if (packet_header[16] == Pacotes.PAYLOAD_OF_PUSH)
                    {
                        System.out.println("Push recebido!");

                        int push_size = Manipuladores.byteArrayToInt(packet_header, 19);
                        System.out.println("push_size:" + push_size);

                        byte[] push_buff = new byte[push_size];
                        gnuDIS[i].read(push_buff);
                        System.out.println("Push:" + Manipuladores.extrairDados(push_buff, 2, push_size) + ";");
                    }
                }
            }
        } catch (IOException ex) {
            if (i != -1)
            {
                System.out.println("SERVIDOR: Conexao perdida com servente " + i + "!");
                removerSocket(gnutellaCloud[i]);
            }
        }
        return true;
    }





    public Socket getSockets(InetAddress addr) {

        int i = 0;
        while(i < posicaoCloud) {
            if(gnutellaCloud[i].getInetAddress().toString().compareTo(String.valueOf(addr)) == 0)
                return gnutellaCloud[i];
            i++;
        }
        return null;
    }

    public boolean setSockets(Socket s) {
        try {
            if(posicaoCloud < Conexoes.MAX_CONEXOES_G2) {
                gnutellaCloud[posicaoCloud] = s;
                gnuDIS[posicaoCloud] = new DataInputStream(s.getInputStream());
                gnuDOS[posicaoCloud] = new DataOutputStream(s.getOutputStream());
                posicaoCloud++;
                System.out.println("total_con:" + posicaoCloud + "     max_con:" + Conexoes.MAX_CONEXOES_G2);
                return true;
            }
        } catch (IOException ex) {
            Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("total_con:" + posicaoCloud + "     max_con:" + Conexoes.MAX_CONEXOES_G2);
            return false;
        }
        System.out.println("total_con:" + posicaoCloud + "     max_con:" + Conexoes.MAX_CONEXOES_G2);
        return false;
    }

    public boolean removerSocket(Socket s) {

        int i = 0;
        boolean removido = false;
        while(i < posicaoCloud) {

            if(gnutellaCloud[i].getInetAddress().toString().compareTo(String.valueOf(s.getInetAddress().toString())) == 0)
                while(i < posicaoCloud) {
                    removido = true;
                    try {
                        gnutellaCloud[i].close();
                        gnuDIS[i].close();
                        gnuDOS[i].close();

                    } catch (IOException ex) {
                        Logger.getLogger(Gerenciador.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    gnutellaCloud[i] = gnutellaCloud[i+1];
                    i++;
                }

            if(removido)
            {
                this.posicaoCloud--;
                return true;
            }

            i++;
        }
        return false;
    }

}